﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Xml;
using System.Xml.Linq;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace XMLViewer
{
    public partial class XmlNodeDisplay : UserControl
    {
        private XDocument _node;

        private const double INDENTATION = 22;

        public XDocument Node
        {
            get { return _node; }
            set
            {
                _node = value;
                Reset();
                LoadRootNodeValues(_node);
                this.Visibility = Visibility.Visible;
            }
        }

        public XmlNodeDisplay()
        {
            InitializeComponent();
        }

        public XmlNodeDisplay(XDocument node)
        {
            InitializeComponent();
            this.Node = node; //used to guarantee LoadRootNodeValues
        }

        //reset the hidden elements to their original state
        public void Reset()
        {
            //debind the grid
            NodeAttributes.ItemsSource = null;
            //clear any value text
            NodeValue.Text = null;         
            //remove childe xmlnodedisplays
            NodeChildren.Children.Clear();

            //rehide all the elements so they can be evaluated properly
            SetElementVisibility(Attributes, Visibility.Collapsed);
            SetElementVisibility(Value, Visibility.Collapsed);
            SetElementVisibility(Children, Visibility.Collapsed);
            SetElementVisibility(NodeChildren, Visibility.Collapsed);
        }
 
        //Handle click of NodeAttribues Stack Panel
        private void ShowNodeAttributes_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            //Show attributes stackpanel
            ToggleVisibility(NodeAttributes);
        }

        //Handle click of NodeChildren Stackpanel 
        private void ShowNodeChildren_MouseLeftButtonUp(object Sender, MouseButtonEventArgs e)
        {
            //if our node has children and if we haven't loaded anything yet
            if (_node.Root.HasElements && NodeChildren.Children.Count == 0)
                LoadNodeChildren(_node);

            ToggleVisibility(NodeChildren);
               
        }

        //Load the Name, Attribues, and Data if there is any
        private void LoadRootNodeValues(XDocument node)
        {
            if (node == null)
                throw new NullReferenceException("The node passed to LoadRootNodeValues must not be null.");

            IEnumerable<XAttribute> attributeCollection;

            //Load the name of this node
            NodeName.Text = node.Root.Name.ToString();

            //If there are no descendants & the value isn't an empty string
            if (_node.Document.Root.Descendants().Count() == 0 && node.Root.Value != String.Empty)
            {
                NodeValue.Text =  node.Root.Value;
                SetElementVisibility(Value, Visibility.Visible);
            }

            //if there are attribues bind them to the NodeAttributs and display
            attributeCollection = node.Document.Root.Attributes();
            if (attributeCollection.Count() > 0)
            {
                IEnumerable<XmlAttribute> xmlAttributes =   from a in attributeCollection
                                                            orderby a.Name.ToString()
                                                            select new XmlAttribute(a.Name.ToString() , a.Value.ToString());
                NodeAttributes.ItemsSource = xmlAttributes;
                SetElementVisibility(Attributes, Visibility.Visible);
            }

            //if the node this control represents has chilren present clickable area
            //for lazy load of children
            if (node.Root.HasElements)
                SetElementVisibility(Children, Visibility.Visible);
        }

        //Load the children of the node. Works by taking the first Descendant
        //which is assumed to be an immediate child of node and gathers all its siblings
        private void LoadNodeChildren(XDocument node)
        {
            IEnumerable<XNode> childNodes = node.Root.DescendantNodes();
            XNode child = childNodes.First();
            
            //while there are still more siblings to process
            while(child != null)
            {
                //add a new XmlNodeDisplay to the NodeChildren stackpanel
                AddChildXmlNodeDisplay(child.ToString(), ref NodeChildren);

                //get the next sibling
                child = child.NextNode;
            }
        }

        private void ToggleVisibility(FrameworkElement element)
        {
            Visibility toggledValue;

            toggledValue = (element.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible);
            SetElementVisibility(element, toggledValue);
        }

        private void SetElementVisibility(FrameworkElement element, Visibility value)
        {
            Border parentBorder = new Border();
            if (element.Parent is Border)
            {
                parentBorder = (Border)element.Parent;
                parentBorder.Visibility = value;
            }

            element.Visibility = value;
        }
        
        //Create a new XMLNodeDisplay and add it to the StackPanel element passed
        private void AddChildXmlNodeDisplay(String xml, ref StackPanel panel)
        {
            XDocument node;
            XmlNodeDisplay newXmlDisp;
            
            //Prevent addition in the case that we don't have xml
            try
            {
                node = XDocument.Parse(xml);
                newXmlDisp = new XmlNodeDisplay(node);

                //set the size to prevent to large or two small
                newXmlDisp.MainStackPanel.MinWidth = this.MainStackPanel.MinWidth - INDENTATION;
                newXmlDisp.MainStackPanel.MaxWidth = this.MainStackPanel.MinWidth - INDENTATION;

                //add it to the children stackpanel
                panel.Children.Add(newXmlDisp);
            }
            catch 
            { 
                //the xml passed is the bottom most data
            }
        }
    }
}
